package com.gaofans.vertx.boot.dbutils.mapper;

import com.gaofans.vertx.boot.dbutils.mapper.domain.Entity;
import com.gaofans.vertx.boot.dbutils.mapper.domain.PropertyRef;
import com.gaofans.vertx.boot.dbutils.mapper.domain.ResultMapper;
import com.google.common.collect.Lists;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import org.springframework.beans.BeanUtils;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;


/**
 * 1.一个返回list的方法，传入rowset，entity
 * 2.根据entity的id对rowset分组
 * 3.循环分组，每个分组取第一个创建entity实例
 * 4.对entity中的每一个单体对集合映射执行该方法的递归操作，单体取list第一个，集合直接赋值
 * 5.返回分组的循环结果
 * @author gaofans
 */
public class BeanProcessor {

    public final static List<ColumnMapper> mappers = new ArrayList<>();

    static {
        for (final ColumnMapper handler : ServiceLoader.load(ColumnMapper.class)) {
            mappers.add(handler);
        }
    }

    private BeanProcessor() {
    }

    public static <T> T toBean(RowSet<Row> rs, ResultMapper mapper, Class<T> tClass) {
        List<T> ts = toList(rs, mapper, tClass);
        Assert.isTrue(ts.size() == 1,"找个多个结果集!");
        return ts.get(0);
    }

    public static <T> List<T> toList(RowSet<Row> rs, ResultMapper mapper,Class<T> tClass){
        try {
            return build(Lists.newArrayList(rs),mapper,tClass);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static class GroupKey {
        private final List<Object> ids;
        public GroupKey() {
            this.ids = new ArrayList<>();
        }
        public void addId(Object o){
            if(o != null){
                this.ids.add(o);
            }
        }
        public List<Object> getIds() {
            return ids;
        }

        @Override
        public int hashCode() {
            return Objects.hash(ids);
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof GroupKey){
                GroupKey other = (GroupKey) obj;
                return other.getIds().containsAll(this.getIds())
                        && this.getIds().containsAll(other.getIds());
            }
            return super.equals(obj);
        }
    }

    private static Map<GroupKey, List<Row>> group(List<Row> rs, Entity entity, Class<?> type){
        Map<GroupKey, List<Row>> group = new LinkedHashMap<>();
        Assert.notEmpty(entity.getIds(),"至少要有一个id");
        for (Row row : rs) {
            GroupKey groupKey = new GroupKey();
            for (PropertyRef ref : entity.getIds()) {
                Object columnValue = findColumnValue(BeanUtils.findPropertyType(ref.getProperty(),type), row, ref.getColumn());
                groupKey.addId(columnValue);
            }
            if(groupKey.getIds().size() == 0){
                continue;
            }
            List<Row> rows = group.get(groupKey);
            if(rows == null){
                rows = new ArrayList<>();
                rows.add(row);
                group.put(groupKey,rows);
            }else{
                rows.add(row);
            }
        }
        return group;
    }

    private static <T> List<T> build(List<Row> rs, Entity entity,Class<T> type) throws Exception {
        Map<GroupKey, List<Row>> group = group(rs, entity, type);
        List<T> results = new ArrayList<>();
        for (Map.Entry<GroupKey, List<Row>> entry : group.entrySet()) {
            Row row = entry.getValue().get(0);
            T t = type.newInstance();
            for (PropertyRef ref : entity.getIds()) {
                propertySet(type,row,t,ref);
            }
            if(entity.getResults() != null){
                for (PropertyRef ref : entity.getResults()) {
                    propertySet(type,row,t,ref);
                }
            }
            if(entity.getAssociations() != null){
                for (Entity association : entity.getAssociations()) {
                    Class<?> aType = Class.forName(association.getOfType());
                    List<?> associationResult = build(entry.getValue(), association, aType);
                    if(associationResult.size() > 0){
                        throw new IllegalStateException("映射出多个association对象");
                    }
                    propertySet(type,association.getProperty(),t,associationResult.get(0));
                }
            }
            if(entity.getCollections() != null){
                for (Entity collection : entity.getCollections()) {
                    Class<?> aType = Class.forName(collection.getOfType());
                    propertySet(type,collection.getProperty(),t,build(entry.getValue(), collection, aType));
                }
            }
            results.add(t);
        }
        return results;
    }

    private static void propertySet(Class<?> tClass, Row row, Object obj, PropertyRef ref) throws IllegalAccessException, InvocationTargetException {
        PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(tClass, ref.getProperty());
        if(descriptor != null){
            Class<?> propertyType = descriptor.getPropertyType();
            Object columnValue = findColumnValue(propertyType, row, ref.getColumn());
            descriptor.getWriteMethod().invoke(obj,columnValue);
        }
    }

    private static void propertySet(Class<?> tClass, String columnValue,Object obj, Object value) throws IllegalAccessException, InvocationTargetException {
        PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(tClass, columnValue);
        if(descriptor != null){
            descriptor.getWriteMethod().invoke(obj,value);
        }
    }

    private static Object findColumnValue(Class<?> propertyType,Row row,String columnName){
        for (ColumnMapper mapper : mappers) {
           if(mapper.match(propertyType)){
              return mapper.apply(row,columnName);
           }
        }
        return null;
    }

}
